//------------------------------------------------------------------------------
// <copyright file="DotBeerUpdatesDownloader.cs" company="Damian Antonowicz">
//       copyright © 2010 Damian Antonowicz
// </copyright>
// <author>Damian Antonowicz</author>
// <email>poczta@damianantonowicz.pl</email>
//------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DAnton.Blocks.Updater;
using System.ComponentModel;
using System.Threading;
using System.IO;
using System.Net;
using System.Diagnostics;

namespace DotBeer.Business.Entities.Updates
{
    public class DotBeerUpdatesDownloader : IUpdatesDownloader
    {
        #region private members

        SynchronizationContext synchCont;

        private bool isBusy = false;
        private bool downloadUpdates = false;

        private string downloadUrl,
                       updaterPath;

        Int64 bytesReadedInLastSecond = 0;
        double transferRate = 0;

        #endregion

        #region properties

        public bool IsBusy
        {
            get { return this.isBusy; }
            set { }
        }

        public event EventHandler<DownloadUpdatesProgressChangedEventArgs> DownloadUpdatesProgressChanged;
        public event EventHandler<AsyncCompletedEventArgs> DownloadUpdatesCompleted;

        #endregion

        #region constructors

        public DotBeerUpdatesDownloader()
        {
            this.synchCont = SynchronizationContext.Current;
        }

        #endregion

        #region public methods

        public void DownloadUpdatesAsync(string downloadUrl, string updaterPath)
        {
            this.RaisceExceptionIfBusy();
            this.isBusy = true;
            this.downloadUpdates = true;
            this.downloadUrl = downloadUrl;
            this.updaterPath = updaterPath;

            ThreadPool.QueueUserWorkItem((ob) => this.DownloadUpdates());
        }

        public void CancelAsync()
        {
            this.isBusy = false;
            this.downloadUpdates = false;
        }

        #endregion

        #region private methods

        private void DownloadUpdates()
        {
            System.Timers.Timer timer = new System.Timers.Timer(1000);

            Stream streamResponse = null;
            Stream streamLocal = null;

            try
            {
                HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(this.downloadUrl);
                webRequest.Credentials = CredentialCache.DefaultCredentials;

                HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse();
                long fileSize = webResponse.ContentLength;
                long totalMegaBytes = fileSize / 1048576;

                this.OnEventOccured(this.DownloadUpdatesProgressChanged, new DownloadUpdatesProgressChangedEventArgs(0, fileSize, totalMegaBytes, 0, 0));

                streamResponse = webResponse.GetResponseStream();
                streamLocal = new FileStream(this.updaterPath, FileMode.Create, FileAccess.Write);

                long totalBytesReaded = 0;
                int bytesReaded = 0;
                int percentage = 0;
                byte[] downloadBuffer = new byte[2048];

                timer.Elapsed += (sender, args) =>
                    {
                        this.transferRate = this.bytesReadedInLastSecond / 1024;
                        this.bytesReadedInLastSecond = 0;

                        this.OnEventOccured(this.DownloadUpdatesProgressChanged,
                            new DownloadUpdatesProgressChangedEventArgs(percentage, fileSize, totalMegaBytes, totalBytesReaded, transferRate));
                    };

                timer.Start();

                while ((bytesReaded = streamResponse.Read(downloadBuffer, 0, downloadBuffer.Length)) > 0 &&
                       this.downloadUpdates)
                {
                    streamLocal.Write(downloadBuffer, 0, bytesReaded);
                    totalBytesReaded += bytesReaded;
                    bytesReadedInLastSecond += bytesReaded;

                    percentage = (int)((totalBytesReaded * 100) / fileSize);
                }

                this.OnEventOccured(this.DownloadUpdatesCompleted, new AsyncCompletedEventArgs(null, !this.downloadUpdates, null));
            }
            catch (Exception ex)
            {
                this.OnEventOccured(this.DownloadUpdatesCompleted, new AsyncCompletedEventArgs(ex, false, null));
            }
            finally
            {
                if (streamResponse != null)
                    streamResponse.Close();

                if (streamLocal != null)
                    streamLocal.Close();

                timer.Stop();
                this.isBusy = false;
            }
        }

        private void OnEventOccured<T>(EventHandler<T> handler, T args) where T : EventArgs
        {
            System.Action invokeMethod = () =>
                {
                    if (handler != null)
                        handler(this, args);
                };

            if (this.synchCont != null)
            {
                this.synchCont.Post(new SendOrPostCallback((state) =>
                    {
                        invokeMethod();
                    }), null);
            }
            else
                invokeMethod();
        }

        private void RaisceExceptionIfBusy()
        {
            if (this.isBusy)
                throw new InvalidOperationException("class doesn't support multiple async operations");
        }

        #endregion
    }
}
